// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "sandboxed_api/tools/clang_generator/emitter.h"

#include <string>
#include <utility>
#include <vector>

#include "absl/container/flat_hash_set.h"
#include "absl/log/log.h"
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_format.h"
#include "absl/strings/str_join.h"
#include "absl/strings/str_replace.h"
#include "absl/strings/str_split.h"
#include "absl/strings/string_view.h"
#include "absl/strings/strip.h"
#include "clang/AST/Decl.h"
#include "clang/AST/DeclBase.h"
#include "clang/AST/Type.h"
#include "sandboxed_api/tools/clang_generator/diagnostics.h"
#include "sandboxed_api/tools/clang_generator/emitter_base.h"
#include "sandboxed_api/tools/clang_generator/generator.h"
#include "sandboxed_api/tools/clang_generator/types.h"
#include "sandboxed_api/util/status_macros.h"

namespace sapi {

// Common header description with auto-generation notice.
constexpr absl::string_view kHeaderDescription =
    R"(// AUTO-GENERATED by the Sandboxed API Clang tool.
// Edits will be discarded when regenerating this file.
)";

// Common header file prolog with auto-generation notice.
// Note: The includes will be adjusted by Copybara when converting to/from
//       internal code. This is intentional.
// Text template arguments:
//   1. Header guard
constexpr absl::string_view kHeaderIncludes =
    R"(
#include "absl/base/macros.h"
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "sandboxed_api/sandbox.h"
#include "sandboxed_api/util/status_macros.h"
#include "sandboxed_api/vars.h"

)";

// Text template arguments:
//   1. Include for embedded sandboxee objects
constexpr absl::string_view kEmbedInclude = R"(#include "%1$s_embed.h"

)";

// Text template arguments:
//   1. Class name
//   2. Embedded object identifier
constexpr absl::string_view kEmbedClassTemplate = R"(
// Sandbox with embedded sandboxee and default policy
class %1$s : public ::sapi::Sandbox {
 public:
  %1$s()
      : ::sapi::Sandbox([]() {
          static auto* fork_client_context =
              new ::sapi::ForkClientContext(%2$s_embed_create());
          return fork_client_context;
        }()) {}
};

)";

// Sandboxed API class template.
// Text template arguments:
//   1. Class name
constexpr absl::string_view kClassHeaderTemplate = R"(
// Sandboxed API
class %1$s {
 public:
  explicit %1$s(::sapi::Sandbox* sandbox) : sandbox_(sandbox) {}


  ABSL_DEPRECATED("Call sandbox() instead")
  ::sapi::Sandbox* GetSandbox() const { return sandbox(); }
  ::sapi::Sandbox* sandbox() const { return sandbox_; }
)";

// Sandboxed API class template footer.
constexpr absl::string_view kClassFooterTemplate = R"(
 private:
  ::sapi::Sandbox* sandbox_;
};
)";

// Returns a unique name for a parameter. If `decl` has no name, a unique name
// will be generated in the form of `unnamed<index>_`.
std::string GetParamName(const clang::ParmVarDecl* decl, int index) {
  if (std::string name = decl->getName().str(); !name.empty()) {
    return absl::StrCat(name, "_");  // Suffix to avoid collisions
  }
  return absl::StrCat("unnamed", index, "_");
}

// Returns a comment for the given function `decl` which represents the
// unsandboxed function signature.
absl::StatusOr<std::string> PrintFunctionPrototypeComment(
    const TypeMapper& type_mapper, const clang::FunctionDecl* decl) {
  std::string out = absl::StrCat(
      type_mapper.MapQualTypeParameterForCxx(decl->getDeclaredReturnType()),
      " ", ToStringView(decl->getName()), "(");

  std::string print_separator;
  for (int i = 0; i < decl->getNumParams(); ++i) {
    const clang::ParmVarDecl* param = decl->getParamDecl(i);

    absl::StrAppend(&out, print_separator);
    print_separator = ", ";
    absl::StrAppend(&out,
                    type_mapper.MapQualTypeParameterForCxx(param->getType()));
    if (std::string name = param->getName().str(); !name.empty()) {
      absl::StrAppend(&out, " ", name);
    }
  }
  absl::StrAppend(&out, ")");

  SAPI_ASSIGN_OR_RETURN(std::string formatted,
                        internal::ReformatGoogleStyle(/*filename=*/"input", out,
                                                      /*column_limit=*/75));
  out.clear();
  for (const auto& line : absl::StrSplit(formatted, '\n')) {
    absl::StrAppend(&out, "// ", line, "\n");
  }
  return out;
}

absl::StatusOr<std::string> Emitter::DoEmitFunction(
    const clang::FunctionDecl* decl) {
  TypeMapper type_mapper(decl->getASTContext(), options_.namespace_name);
  const clang::QualType return_type = decl->getDeclaredReturnType();

  // Skip functions returning record by value.
  if (return_type->isRecordType()) {
    return MakeStatusWithDiagnostic(
        decl->getBeginLoc(), absl::StatusCode::kCancelled,
        "Returning record by value, skipping function.");
  }

  SAPI_ASSIGN_OR_RETURN(std::string prototype,
                        PrintFunctionPrototypeComment(type_mapper, decl));
  std::string out;
  absl::StrAppend(&out, "\n", prototype);

  auto function_name = ToStringView(decl->getName());
  const bool returns_void = return_type->isVoidType();

  absl::StrAppend(&out, type_mapper.MapQualTypeReturn(return_type), " ",
                  function_name, "(");

  struct ParameterInfo {
    clang::QualType qual;
    std::string name;
  };
  std::vector<ParameterInfo> params;

  // Process the function parameter list.
  std::string print_separator;
  for (int i = 0; i < decl->getNumParams(); ++i) {
    const clang::ParmVarDecl* param = decl->getParamDecl(i);

    // Skip functions with record parameters passed by value.
    if (param->getType()->isRecordType()) {
      return MakeStatusWithDiagnostic(
          param->getBeginLoc(), absl::StatusCode::kCancelled,
          absl::StrCat("Passing record parameter '",
                       ToStringView(param->getName()),
                       "' by value, skipping function."));
    }

    ParameterInfo& param_info = params.emplace_back();
    param_info.qual = param->getType();
    param_info.name = GetParamName(param, i);

    absl::StrAppend(&out, print_separator);
    print_separator = ", ";
    absl::StrAppend(&out, type_mapper.MapQualTypeParameter(param_info.qual),
                    " ", param_info.name);
  }

  absl::StrAppend(&out, ") {\n");

  // Declare the return value of the SAPI function.
  absl::StrAppend(&out, type_mapper.MapQualType(return_type), " v_ret_;\n");

  // Declare the local variables for the parameters.
  for (const auto& [qual, name] : params) {
    if (!IsPointerOrReference(qual)) {
      absl::StrAppend(&out, type_mapper.MapQualType(qual), " v_", name, "(",
                      name, ");\n");
    }
  }

  // Call the sandboxed function.
  absl::StrAppend(&out, "\nSAPI_RETURN_IF_ERROR(sandbox_->Call(\"",
                  function_name, "\", &v_ret_");
  for (const auto& [qual, name] : params) {
    absl::StrAppend(&out, ", ", IsPointerOrReference(qual) ? "" : "&v_", name);
  }

  // End the sandboxed function call and return `ok` if the unsandboxed function
  // returns void, or else return the value of the SAPI function.
  absl::StrAppend(&out, "));\nreturn ",
                  (returns_void ? "::absl::OkStatus()" : "v_ret_.GetValue()"),
                  ";\n}\n");
  return out;
}

absl::StatusOr<std::string> Emitter::DoEmitHeader() {
  // Log a warning message if the number of requested functions is not equal to
  // the number of functions generated.
  if (!options_.function_names.empty() &&
      (options_.function_names.size() != rendered_functions_ordered_.size())) {
    LOG(WARNING) << "Generated output has fewer functions than expected - some "
                    "function signatures might use language features that "
                    "SAPI does not support. For debugging, we recommend you "
                    "compare the list of functions in your sapi_library() rule "
                    "with the generated *.sapi.h file. Expected: "
                 << options_.function_names.size()
                 << ", generated: " << rendered_functions_ordered_.size();
  }
  std::string out;
  const std::string include_guard = GetIncludeGuard(options_.out_file);
  absl::StrAppend(&out, kHeaderDescription);
  absl::StrAppendFormat(&out, kHeaderProlog, include_guard);

  // Emit the collected includes.
  absl::StrAppend(&out, absl::StrJoin(rendered_includes_ordered_, "\n"));

  // Emit the common includes.
  absl::StrAppend(&out, kHeaderIncludes);

  // When embedding the sandboxee, add embed header include
  if (!options_.embed_name.empty()) {
    // Not using JoinPath() because even on Windows include paths use plain
    // slashes.
    std::string include_file(absl::StripSuffix(
        absl::StrReplaceAll(options_.embed_dir, {{"\\", "/"}}), "/"));
    if (!include_file.empty()) {
      absl::StrAppend(&include_file, "/");
    }
    absl::StrAppend(&include_file, options_.embed_name);
    absl::StrAppendFormat(&out, kEmbedInclude, include_file);
  }

  // If specified, wrap the generated API in a namespace
  if (options_.has_namespace()) {
    absl::StrAppendFormat(&out, kNamespaceBeginTemplate,
                          options_.namespace_name);
  }

  // Emit type dependencies
  if (!rendered_types_ordered_.empty()) {
    absl::StrAppend(&out, "// Types this API depends on\n");
    absl::string_view last_ns_name;
    const std::string ns_prefix = absl::StrCat(options_.namespace_name, "::");
    for (const RenderedType* rt : rendered_types_ordered_) {
      absl::string_view ns_name = rt->ns_name;
      if (ns_name == options_.namespace_name) {
        ns_name = "";
      } else {
        absl::ConsumePrefix(&ns_name, ns_prefix);
      }

      if (ns_name != last_ns_name) {
        if (!last_ns_name.empty()) {
          absl::StrAppend(&out, "\n}  // namespace ", last_ns_name, "\n");
        }
        if (!ns_name.empty()) {
          absl::StrAppend(&out, "namespace ", ns_name, " {\n");
        }
        last_ns_name = ns_name;
      }

      absl::StrAppend(&out, rt->spelling, ";\n");
    }
    if (!last_ns_name.empty()) {
      absl::StrAppend(&out, "\n}  // namespace ", last_ns_name, "\n");
    }
  }

  // Optionally emit a default sandbox that instantiates an embedded sandboxee
  if (!options_.embed_name.empty()) {
    absl::StrAppendFormat(
        &out, kEmbedClassTemplate, absl::StrCat(options_.name, "Sandbox"),
        absl::StrReplaceAll(options_.embed_name, {{"-", "_"}}));
  }

  // Emit the actual Sandboxed API
  absl::StrAppendFormat(&out, kClassHeaderTemplate,
                        absl::StrCat(options_.name, "Api"));
  absl::StrAppend(&out, absl::StrJoin(rendered_functions_ordered_, "\n"));
  absl::StrAppend(&out, kClassFooterTemplate);

  // Close out the header: close namespace (if needed) and end include guard
  if (options_.has_namespace()) {
    absl::StrAppendFormat(&out, kNamespaceEndTemplate, options_.namespace_name);
  }
  absl::StrAppendFormat(&out, kHeaderEpilog, include_guard);
  return out;
}

absl::Status Emitter::AddFunction(clang::FunctionDecl* decl) {
  if (rendered_functions_.insert(decl->getQualifiedNameAsString()).second) {
    SAPI_ASSIGN_OR_RETURN(std::string function, DoEmitFunction(decl));
    rendered_functions_ordered_.push_back(function);
  }
  return absl::OkStatus();
}

absl::StatusOr<std::string> Emitter::EmitHeader() {
  SAPI_ASSIGN_OR_RETURN(const std::string header, DoEmitHeader());
  return internal::ReformatGoogleStyle(options_.out_file, header);
}

}  // namespace sapi
